
/**
 * Copyright 2009 cestum.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
package org.waveprotocol.wave.model.document.operation.impl {
import org.goverla.collections.ArrayList;
import org.goverla.collections.ListCollectionViewIterator;
import org.waveprotocol.wave.model.document.operation.AnnotationBoundaryMap;
import org.waveprotocol.wave.model.document.operation.Attributes;
import org.waveprotocol.wave.model.document.operation.AttributesUpdate;
import org.waveprotocol.wave.model.document.operation.BufferedDocOp;
import org.waveprotocol.wave.model.document.operation.DocOpComponentType;  
import org.waveprotocol.wave.model.document.operation.util.DocOpComponentConstants;
import org.waveprotocol.wave.model.document.operation.DocumentOperationCursor;
import org.waveprotocol.wave.model.document.operation.automaton.violation.ViolationCollector;
import org.waveprotocol.wave.model.util.Preconditions;

/**
 * to construct this,
 * {@use DocInitializationBuilder}
 * {@use DocOpBuffer}
 * {@use DocInitializationBuffer}
 */
final class BufferedDocOpImpl implements BufferedDocOp {

  private var knownToBeWellFormed:Boolean= false;

  /**
   * Creates a new buffered doc op, checking that it is well-formed.
   */
  static function create(components:ArrayList):BufferedDocOpImpl {
    var op:BufferedDocOpImpl= createUnchecked(components);
    checkWellformedness(op);
    org.waveprotocol.wave.model.util.Assert.assert(op.knownToBeWellFormed);
    return op;
  }

  /**
   * Creates a new buffered doc op without checking for well-formedness.
   *
   * @param components op components
   */
  static function createUnchecked(components:ArrayList):BufferedDocOpImpl {
    return new BufferedDocOpImpl(components);
  }

  /**
   * Checks that a buffered doc op is well-formed.
   *
   * @param value op to check
   * @throws IllegalStateException if the op is ill-formed
   */
  private static function checkWellformedness(value:BufferedDocOp):void {
    if (!DocOpValidator.isWellFormed(null, value)) {
      // Check again, collecting violations this time.
      var v:ViolationCollector= new ViolationCollector();
      DocOpValidator.isWellFormed(v, value);
      throw new Error("Attempt to build ill-formed operation ("
          + v + "): " + value);
    }
  }

  private var components:ArrayList;


  function BufferedDocOpImpl(components:ArrayList) {
    this.components = components;
	
  }

  
  public function size():int {
    return components.length;
  }

  
  public function getType(i:int):DocOpComponentType {
    return components[i].getType();
  }

  
  public function applyComponent(i:int, cursor:DocumentOperationCursor):void {
    components[i].apply(cursor);
  }

  
  public function apply(cursor:DocumentOperationCursor):void {
	var itr:ListCollectionViewIterator = components.createIterator();
	while(itr.hasNext()) {
		var component:DocumentOperationComponent = itr.next() as DocumentOperationComponent;
		component.apply(cursor);
	}
  }

  
  public function getCharactersString(i:int):String {
    check(i, DocOpComponentConstants.CHARACTERS);
    return (Characters(components[i])).string;
  }

  
  public function getDeleteCharactersString(i:int):String {
    check(i, DocOpComponentConstants.DELETE_CHARACTERS);
    return (DeleteCharacters(components[i])).string;
  }

  
  public function getReplaceAttributesNewAttributes(i:int):Attributes {
    check(i, DocOpComponentConstants.REPLACE_ATTRIBUTES);
    return (ReplaceAttributes(components[i])).newAttrs;
  }

  
  public function getReplaceAttributesOldAttributes(i:int):Attributes {
    check(i, DocOpComponentConstants.REPLACE_ATTRIBUTES);
    return (ReplaceAttributes(components[i])).oldAttrs;
  }

  
  public function getRetainItemCount(i:int):int {
    check(i, DocOpComponentConstants.RETAIN);
    return (Retain(components[i])).itemCount;
  }

  
  public function getAnnotationBoundary(i:int):AnnotationBoundaryMap {
    check(i, DocOpComponentConstants.ANNOTATION_BOUNDARY);
    return (AnnotationBoundary(components[i])).boundary;
  }

  
  public function getDeleteElementStartAttributes(i:int):Attributes {
    check(i, DocOpComponentConstants.DELETE_ELEMENT_START);
    return (DeleteElementStart(components[i])).attrs;
  }

  
  public function getDeleteElementStartTag(i:int):String {
    check(i, DocOpComponentConstants.DELETE_ELEMENT_START);
    return (DeleteElementStart(components[i])).type;
  }

  
  public function getElementStartAttributes(i:int):Attributes {
    check(i, DocOpComponentConstants.ELEMENT_START);
    return (ElementStart(components[i])).attrs;
  }

  
  public function getElementStartTag(i:int):String {
    check(i, DocOpComponentConstants.ELEMENT_START);
    return (ElementStart(components[i])).type;
  }

  
  public function getUpdateAttributesUpdate(i:int):AttributesUpdate {
    check(i, DocOpComponentConstants.UPDATE_ATTRIBUTES);
    return (UpdateAttributes(components[i])).update;
  }

  /**
   * @return true if wellfomed operation
   */
  public function isKnownToBeWellFormed():Boolean {
    return knownToBeWellFormed;
  }

  function markWellFormed():void {
    knownToBeWellFormed = true;
  }

  private function check(i:int, type:DocOpComponentType):void {
    Preconditions.checkArgument(components[i].getType() == type,
        "Component " + i + " is not of type ' " + type + "', " +
        "it is '" + components[i].getType() + "'");
  }

  
  public function toString():String {
    return DocumentOperationUtil.toConciseString(this);
  }
}
}